package com.ksana.bean.factory.support;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import com.ksana.bean.BeanException;
import com.ksana.bean.PropertyValue;
import com.ksana.bean.factory.*;
import com.ksana.bean.factory.config.*;
import com.ksana.util.ClassUtil;
import com.ksana.util.StringUtil;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Parameter;

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
    private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeanException {
        Object bean = null;

        bean = resolveBeforeInstantiation(beanName, beanDefinition);

        if (bean != null) {
            return bean;
        }

        bean = createBeanInstance(beanName, beanDefinition, args);
        bean = initializeBean(beanName, bean, beanDefinition);
        applyBeanPropertyValues(bean, beanDefinition);
        registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);
        return bean;
    }

    protected Object resolveBeforeInstantiation(String beanName, BeanDefinition beanDefinition) {
        Object bean = applyBeanPostProcessorsBeforeInstantiation(beanDefinition.getBean(), beanName);
        if (null != bean) {
            bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
        }
        return bean;
    }

    protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                Object result = ((InstantiationAwareBeanPostProcessor) bp).postProcessBeforeInstantiation(beanClass, beanName);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }

    private Object createBeanInstance(String name, BeanDefinition beanDefinition, Object[] args) {
        if (args == null) {
            return instantiationStrategy.instantiate(beanDefinition, name);
        }

        Class clazz = beanDefinition.getBean();
        Constructor ctorToUser = null;
        Constructor[] constructors = clazz.getConstructors();

        for (Constructor ctor : constructors) {
            if (ctor.getParameterTypes().length == args.length) {
                ctorToUser = ctor;
                break;
            }
        }

        return instantiationStrategy.instantiate(beanDefinition, name, ctorToUser, args);
    }

    protected Object initializeBean(String beanName, Object bean, BeanDefinition mbd) {
        try {
            invokeAwareMethods(bean, beanName);
            bean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);
            invokeInitMethods(beanName, bean, mbd);
            bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        }

        return bean;
    }

    protected void invokeInitMethods(String beanName, Object bean, BeanDefinition mbd) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Boolean isInitializingBean = (bean instanceof InitializingBean);

        if (isInitializingBean) {
            ((InitializingBean) bean).init();
        } else if (!StringUtil.isNotEmpty(mbd.getInitMethodName())) {
            String InitializingMethodName = mbd.getInitMethodName();
            mbd.getBean().getMethod(InitializingMethodName).invoke(bean);
        }
    }

    public void applyBeanPropertyValues(Object bean, BeanDefinition beanDefinition) throws BeanException {
        for (PropertyValue propertyValue : beanDefinition.getPropertyValues().getPropertyValues()) {
            Object value = null;
            String name = propertyValue.getName();

            if (propertyValue.getValue() instanceof BeanReference) {
                value = getBean(((BeanReference) propertyValue.getValue()).getName());
            } else {
                value = propertyValue.getValue();
            }

            BeanUtil.setFieldValue(bean, name, value);
        }
    }

    public void registerDisposableBeanIfNecessary(String beanName, Object bean, BeanDefinition mbd) {
        registerDisposableBeans(beanName, new DisposableBeanAdapter(beanName, bean, mbd));
    }

    @Override
    public Object applyBeanPostProcessorsBeforeInitialization(Object bean, String beanName) {
        Object result = bean;

        for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) {
            Object current = beanPostProcessor.postProcessBeforeInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }

        return result;
    }

    @Override
    public Object applyBeanPostProcessorsAfterInitialization(Object bean, String beanName) {
        Object result = bean;

        for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) {
            Object current = beanPostProcessor.postProcessAfterInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }

        return result;
    }

    protected void invokeAwareMethods(Object bean, String beanName) {
        if (bean instanceof Aware) {
            if (bean instanceof BeanNameAware) {
                ((BeanNameAware) bean).setBeanName(beanName);
            }

            if (bean instanceof BeanClassLoaderAware) {
                ((BeanClassLoaderAware) bean).setBeanClassLoader(ClassUtil.getClassLoader());
            }

            if (bean instanceof BeanFactoryAware) {
                ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
            }
        }
    }
}
